/*
 * htprefetch.c
 *
 *  Created on: Nov 25, 2011
 *      Author: bendischinger
 */

#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <strings.h>
#include <pthread.h>
#include <time.h>

#include "block_matrix.h"
#include "block_matrix_helper.h"
#include "htprefetch.h"

#define __MAIN
#include "helper_thread.h"
#include "test_stat.h"

#define IN_MAX 2
#define OUT_MAX 1
#define DEFAULT_CACHE_SIZE 1<<14

static int is_warm = 0;

void usage(char *error) {
	if (error) {
		printf("%s\n%s\n", error, usage_str);
	} else {
		printf("%s\n", usage_str);
	}
	exit(1);
}

int process_mat_args(block_matrix_t *mat, int argc, char **argv) {
	int num_args = 0;

	if (ht_active) {
		mat->flags |= M_PREFETCH;
		mat->blk_cache_size = DEFAULT_CACHE_SIZE;
	}

	while (argc-- > 0) {
		char *opt = *argv++;
		if (strcmp(opt, "-a") == 0) {
			mat->flags |= M_AIO;
		} else if (strcmp(opt, "-c") == 0) {
			mat->flags |= M_BLK_CACHE;
			mat->blk_cache_size = DEFAULT_CACHE_SIZE;
			if (argc > 0 && **argv != '-') {
				mat->blk_cache_size = (int)strtol(*argv++, NULL, 10);
				argc--;
				num_args++;
			}
		} else if (strcmp(opt, "-d") == 0) {
			mat->flags |= M_DIRECT_IO;
		} else {
			break;
		}
		num_args++;
	}

	return (num_args);
}

void print_test_result(char *op, block_matrix_t *mat, test_stat_t *stat) {
	char buf[1024];
	snprintf(buf, 1024, "%s%s%s%s%s", op,
		mat->flags & M_BLK_CACHE ? " cache" : "",
		mat->flags & M_DIRECT_IO ? " direct" : is_warm ? " warm" : " cold",
		mat->flags & M_AIO ? " aio" : "",
		mat->flags & M_PREFETCH ? " prefetch" : "");

	printf("\"%s\", %d, %d,   "
		"%f, %f, %f, %f,   "
		"%d, %d, %d,   "
		"%f, %f,   "
		"%d, %d, %d,   "
		"%f, %f, %f,   "
		"%f, %f, %f,   "
		"%d, %d, %d, %d,   "
		"%d, %d, %d,   "
		"%d, %d\n",
		buf,
		mat->blk_cols*mat->blk_rows*mat->elem_cols*mat->elem_rows,
		mat->elem_cols*mat->elem_rows,
		stat->test_time, stat->read_time, stat->write_time, stat->op_time,
		stat->read_cnt, stat->write_cnt, stat->op_cnt,
		stat->helper_read_time, stat->helper_time,
		stat->helper_read_cnt, stat->helper_behind_cnt, stat->helper_ahead_cnt,
		stat->max_read_time, stat->max_write_time, stat->max_op_time,
		stat->min_read_time, stat->min_write_time, stat->min_op_time,
		stat->cache_hit, stat->cache_miss, stat->cache_pop, stat->cache_col,
		stat->aio_err, stat->aio_wait, stat->aio_eagain,
		ht_hwm, ht_lwm);
}

void *helper_start(void *args) {
	helper_start_arg_t *hs_arg = args;
	test_time_t start = start_timer();
	hs_arg->helper_func(hs_arg->mat);
	global_stat.helper_time = stop_timer(start, UNIT_USEC);
	return NULL;
}

int
main(int argc, char **argv) {
	int i, op, search_item, mat_type, verbose;
	block_matrix_ij_t search_result;
	test_time_t start_time;
	char *req_op;

	pthread_t ht_id;
	helper_start_arg_t hs_arg;
	block_matrix_t *mat[3];
	hs_arg.helper_func = NULL;
	hs_arg.mat = mat;

	verbose = mat_type = search_item = 0;

	int in_num = 0;
	char *in_file[IN_MAX];
	block_matrix_t in_mat[IN_MAX];

	int out_num = 0;
	char *out_file[OUT_MAX];
	block_matrix_t out_mat[OUT_MAX];

	bzero(in_mat, IN_MAX * sizeof (block_matrix_t));
	bzero(out_mat, OUT_MAX * sizeof (block_matrix_t));

	if (--argc < 1) usage(NULL);
	argv++;

	srandom(time(NULL));
	init_test_stat(&global_stat);

	/* Find the operation */
	req_op = *argv++;
	argc--;
	for (op=0; op<OP_NOT_FOUND; op++) {
		if (strcmp(op_str[op], req_op) == 0) {
			break;
		}
	}
	if (op == OP_NOT_FOUND) {
		usage("Invalid operation");
	}

	/* Process operation arguments */
	switch (op) {
		case SEARCH:
			if (argc-- < 1) usage("Too few search parameters");
			search_item = (int)strtol(*argv++, NULL, 10);
			break;
		case CREATE:
		{
			int blk_dim, elem_dim, type;
			if (argc < 3) usage("Too few create parameters");
			blk_dim = (int)strtol(*argv++, NULL, 10);
			elem_dim = (int)strtol(*argv++, NULL, 10);
			for (type=0; type<TYPE_NOT_FOUND; type++) {
				if (strcmp(mat_type_str[type], *argv) == 0) {
					argv++;
					argc -= 3;
					break;
				}
			}
			if (blk_dim <= 0 || elem_dim <= 0 || type==TYPE_NOT_FOUND) {
				usage("Too few operations for create");
			}
			/* Init square matrix */
			init_block_matrix(&out_mat[0], blk_dim, blk_dim, elem_dim, elem_dim);
			out_mat->flags |= M_CREATE;
			mat_type = type;
			break;
		}
		case PRINT:
			verbose++;
			break;
	}

	/* Process optional arguments */
	while (argc > 0 && (strcmp(*argv, "-p") == 0 ||
		strncmp(*argv, "-v", 2) == 0 ||
		strcmp(*argv, "-w") == 0)) {
		switch ((*argv)[1]) {
			case 'p':
				HT_INIT();
				/* Check for optional lwm/hwm */
				if (*argv[1] != '-') {
					if (argc < 3) usage("Both low and high are required");
					ht_lwm = (int)strtol(*++argv, NULL, 10);
					ht_hwm = (int)strtol(*++argv, NULL, 10);
					argc -= 2;
					if (ht_lwm >= ht_hwm) {
						usage("Low water mark must be smaller than high water mark");
					}
				}
				break;
			case 'v':
				verbose++;
				if ((*argv)[2] == 'v') verbose++;
				break;
			case 'w':
				is_warm = 1;
				break;
		}
		argv++;
		argc--;
	}

	/* Process file arguments */
	while (argc > 1) {
		char *param = *argv++;
		char *file = *argv++;
		int proc_args;
		argc -= 2;
		if (strcmp(param, "-in") == 0) {
			if (in_num >= IN_MAX) {
				usage("Too many -in parameters");
			}
			proc_args = process_mat_args(&in_mat[in_num], argc, argv);
			in_file[in_num++] = file;
		} else if (strcmp(param, "-out") == 0) {
			if (out_num >= OUT_MAX) {
				usage("Too many -out parameters");
			}
			proc_args = process_mat_args(&out_mat[out_num], argc, argv);
			out_file[out_num++] = file;
		} else {
			usage("Expected -in or -out");
		}
		argc -= proc_args;
		argv += proc_args;
	}

	if (argc > 0) {
		usage("Unexpected arguments");
	}

	for (i=0; i<in_num; i++) {
		if (open_block_matrix(in_file[i], &in_mat[i], in_mat[i].flags, in_mat[i].blk_cache_size)) {
			printf("Error opening %s: %s (%d)\n", in_file[i], strerror(errno), errno);
			exit(1);
		}
	}

	/* Parsing successful, initialize matrices */
	switch (op) {
		case ADD:
		case MULTIPLY:
			mat[0] = &in_mat[0];
			mat[1] = &in_mat[1];
			mat[2] = &out_mat[0];
			init_block_matrix(&out_mat[0],
				in_mat[0].blk_cols, in_mat[1].blk_rows,
				in_mat[0].elem_cols, in_mat[1].elem_rows);
			hs_arg.helper_func = add_matrix_helper;
			if (op == MULTIPLY) {
				hs_arg.helper_func = multiply_matrix_helper;
			}
			break;
		case TRANSPOSE:
			mat[0] = &in_mat[0];
			mat[1] = &out_mat[0];
			init_block_matrix(&out_mat[0],
				in_mat[0].blk_cols, in_mat[0].blk_rows,
				in_mat[0].elem_cols, in_mat[0].elem_rows);
			//helper_func = transpose_matrix_helper;
			break;
		case SEARCH:
			mat[0] = &in_mat[0];
			//helper_func = search_matrix_helper;
			break;
		case CREATE:
			mat[0] = &out_mat[0];
			break;
	}


	for (i=0; i<out_num; i++) {
		if (open_block_matrix(out_file[i], &out_mat[i], out_mat[i].flags|M_CREATE, out_mat[i].blk_cache_size)) {
			printf("Error opening %s: %s (%d)\n", out_file[i], strerror(errno), errno);
			exit(1);
		}
	}

	/* Helper Thread Initialization */
	if (ht_active && hs_arg.helper_func) {
		pthread_create(&ht_id, NULL, helper_start, &hs_arg);
	}

	start_time = start_timer();
	/* Execute Operation */
	switch (op) {
		case ADD:
			add_matrix(&in_mat[0], &in_mat[1], &out_mat[0]);
			break;
		case MULTIPLY:
			multiply_matrix(&in_mat[0], &in_mat[1], &out_mat[0]);
			break;
		case TRANSPOSE:
			transpose_matrix(&in_mat[0], &out_mat[0]);
			break;
		case SEARCH:
			search_matrix(&in_mat[0], search_item, &search_result);
			printf("Search Result: (%d, %d, %d, %d)\n",
				search_result.blk_i, search_result.blk_j,
				search_result.elem_i, search_result.elem_j);
			break;
		case CREATE:
			switch (mat_type) {
				case TYPE_RANDOM: populate_random_matrix(&out_mat[0]); break;
				case TYPE_ORDERED: populate_ordered_matrix(&out_mat[0]); break;
				case TYPE_IDENTITY: populate_identity_matrix(&out_mat[0]); break;
			}
			break;
	}
	global_stat.test_time = stop_timer(start_time, UNIT_USEC);

	if (ht_active) {
		pthread_join(ht_id, NULL);
	}

	if (verbose > 1) {
		for (i=0; i<in_num; i++) {
			printf("\nMatrix in[%d]: %s\n", i, in_file[i]);
			print_matrix(&in_mat[i]);
		}
		for (i=0; i<out_num; i++) {
			printf("\nMatrix out[%d]: %s\n", i, out_file[i]);
			print_matrix(&out_mat[i]);
		}
	} else if (verbose) {
		for (i=0; i<in_num; i++) {
			printf("\nMatrix in[%d]: %s\n", i, in_file[i]);
			print_matrix_header(&in_mat[i]);
		}
		for (i=0; i<out_num; i++) {
			printf("\nMatrix out[%d]: %s\n", i, out_file[i]);
			print_matrix_header(&out_mat[i]);
		}
	}

	print_test_result(req_op, in_num > 0 ? &in_mat[0] : &out_mat[0], &global_stat);

	for (i=0; i<in_num; i++) {
		close_block_matrix(&in_mat[i]);
	}
	for (i=0; i<out_num; i++) {
		close_block_matrix(&out_mat[i]);
	}

	return (0);
}

